Setup

Versioning

Last updated on 2025-Feb-10 at 02:37 PM.

  • 2025-Feb-07: Created git & RStudio project
  • 2025-Feb-10: Added raw and filtered plots

Acknowledgements

Data collected by Research Assistants Hailey Burns, Kyle McVea, and Carol Thomas.


# load packages
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(here)
## here() starts at /Users/eadie/EadieTech/gsr-study
library(DT)
library(plotly)
## 
## Attaching package: 'plotly'
## 
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## 
## The following object is masked from 'package:stats':
## 
##     filter
## 
## The following object is masked from 'package:graphics':
## 
##     layout
library(viridis)
## Loading required package: viridisLite
library(svglite)
library(htmltools)
library(signal) # For butterworth filter
## 
## Attaching package: 'signal'
## 
## The following object is masked from 'package:plotly':
## 
##     filter
## 
## The following object is masked from 'package:dplyr':
## 
##     filter
## 
## The following objects are masked from 'package:stats':
## 
##     filter, poly

sessionInfo()
## R version 4.4.2 (2024-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS Ventura 13.7.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/Halifax
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] signal_1.8-1      htmltools_0.5.8.1 svglite_2.1.3     viridis_0.6.5    
##  [5] viridisLite_0.4.2 plotly_4.10.4     DT_0.33           here_1.0.1       
##  [9] lubridate_1.9.4   forcats_1.0.0     stringr_1.5.1     dplyr_1.1.4      
## [13] purrr_1.0.4       readr_2.1.5       tidyr_1.3.1       tibble_3.2.1     
## [17] ggplot2_3.5.1     tidyverse_2.0.0  
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.9        generics_0.1.3    stringi_1.8.4     hms_1.1.3        
##  [5] digest_0.6.37     magrittr_2.0.3    evaluate_1.0.3    grid_4.4.2       
##  [9] timechange_0.3.0  fastmap_1.2.0     rprojroot_2.0.4   jsonlite_1.8.9   
## [13] gridExtra_2.3     httr_1.4.7        scales_1.3.0      lazyeval_0.2.2   
## [17] jquerylib_0.1.4   cli_3.6.3         rlang_1.1.5       munsell_0.5.1    
## [21] withr_3.0.2       cachem_1.1.0      yaml_2.3.10       tools_4.4.2      
## [25] tzdb_0.4.0        colorspace_2.1-1  vctrs_0.6.5       R6_2.5.1         
## [29] lifecycle_1.0.4   htmlwidgets_1.6.4 MASS_7.3-64       pkgconfig_2.0.3  
## [33] pillar_1.10.1     bslib_0.9.0       gtable_0.3.6      glue_1.8.0       
## [37] data.table_1.16.4 systemfonts_1.2.1 xfun_0.50         tidyselect_1.2.1 
## [41] rstudioapi_0.17.1 knitr_1.49        rmarkdown_2.29    compiler_4.4.2

# default chunk options
knitr::opts_chunk$set(
  comment = '>', cache = TRUE, collapse = TRUE, cache = FALSE, dev= c("png")
  )

# load processed data
#load(here("dBdat.Rda"))

Study summary

We examined GSR data in healthy controls while participants were doing a visual field test (VFT).

Methods (briefly)

Subjects

Eligible participants were identified among patients of Dr. Brennan Eadie at the Halifax Vision Centre. If deemed eligible for the study, subjects were recruited consecutively.

Each participant underwent 3 study visits, whereupon they performed a VF test on the eligible eye.

The study adhered to the tenets of the Declaration of Helsinki for research involving human subjects and the protocol was approved by the Nova Scotia Health Research Ethics Board (#1027265). All participants gave their written informed consent before enrollment in the study.

Analysis

Not part of this analysis are datasets from:

  • healthy participants using neutral density filter
  • patients with glaucoma

Also not looking at heart rate, BP, questionnaire data right now.

Plots

Raw GSR data

Best viewed on a computer screen. Scroll or swipe (if you’re on mobile) from right to left to view the next plot.

df <- tibble(files = list.files(path = "data", pattern = "csv", full.names = T)) %>%
    mutate(dfs = map(files, read_csv, skip = 7, col_names=F, show_col_types=FALSE))

plot_list <- lapply(1:nrow(df), function(i) {
  data <- df$dfs[[i]]
  
  # Rename columns for clarity (assuming they are currently unnamed)
  colnames(data) <- c("timestamp", "heart_rate", "gsr") 
  
  # data$timestamp <- trimws(data$timestamp)
  data$timestamp <- gsub("^'|'$", "", data$timestamp) # Match start/end quotes
  data$timestamp <- gsub("^-", "", data$timestamp)
  
  data$timestamp_duration <- lubridate::hms(data$timestamp)
  
   # 4. Convert to numeric (seconds):
  data$timestamp_duration <- as.numeric(data$timestamp_duration)
  
  # Create the plot
  ggplot(data, aes(x = timestamp_duration)) +
    # geom_line(aes(y = heart_rate, color = "Heart Rate")) +
    geom_line(aes(y = gsr)) +
    labs(title = df$files[i], 
         x = "Time (s)", 
         y = "GSR (uS)") +
    theme_minimal() 
})

# walk(plot_list, print)

# Convert ggplot objects to plotly objects:
plot_list_plotly <- lapply(plot_list, ggplotly)

# Create the scrolling container:
scrolling_container <- htmltools::tags$div(
  style = "overflow-x: scroll; white-space: nowrap; width: 100%;" # Key styles
)

# Add each plot to the scrolling container:
for (i in 1:length(plot_list_plotly)) {
  scrolling_container$children[[i]] <- htmltools::div(
    style = "display: inline-block; margin-right: 10px;", # Inline and spacing
    plot_list_plotly[[i]]
  )
}

# Display the scrolling container:
scrolling_container

Filtered GSR data

Plots with 2 Hz lowpass filter and moving average filter applied.


# Function to apply lowpass filter:
lowpass_filter <- function(data, cutoff_freq, sample_rate) {
  # Normalize cutoff frequency (Nyquist frequency is 0.5)
  cutoff_normalized <- cutoff_freq / (sample_rate / 2)

  # Butterworth filter design (adjust order as needed)
  bf <- butter(3, cutoff_normalized, type = "low") # 2nd order filter

  # Apply filter to gsr data
  filtered_gsr <- filtfilt(bf, data$gsr) # Zero-phase filtering

  return(filtered_gsr)
}

# Function to apply moving average filter:
moving_average_filter <- function(data, window_size) {
  data_length <- length(data$gsr)

  # Check if window_size is odd
  if (window_size %% 2 == 0) {
    warning("window_size was even. Adjusting to the nearest odd number.")
    window_size <- window_size + 1  # Make it odd
  }

  # Check if window_size is smaller than or equal to data length
  if (window_size >= data_length) {
    warning("window_size is larger than or equal to data length. Reducing window size.")
    window_size <- data_length - (data_length %% 2) # Largest odd number less than data length
  }

  # Now, window_size is GUARANTEED to be odd and LESS THAN data_length

  ma_filter <- rep(1/window_size, window_size)

  # Use 'filter' from the 'stats' package with 'sides = 2' (centered moving average):
  filtered_data <- stats::filter(data$gsr, ma_filter, sides = 2)

  # Handle edge cases by replicating the edge values (same as before):
  half_window <- (window_size - 1) / 2
  filtered_data[1:half_window] <- filtered_data[half_window + 1] # Replicate left edge
  filtered_data[(data_length - half_window + 1):data_length] <- filtered_data[data_length - half_window] # Replicate right edge

  return(filtered_data)
}

# Loop through plot_list, apply filter, and create plots:
plot_list_filtered <- lapply(1:nrow(df), function(i) {
  data <- df$dfs[[i]]
  
  data <- na.omit(data)

  # Rename columns for clarity (assuming they are currently unnamed)
  colnames(data) <- c("timestamp", "heart_rate", "gsr") 
  
  # data$timestamp <- trimws(data$timestamp)
  data$timestamp <- gsub("^'|'$", "", data$timestamp) # Match start/end quotes
  data$timestamp <- gsub("^-", "", data$timestamp)
  
  data$timestamp_duration <- lubridate::hms(data$timestamp)
  
   # 4. Convert to numeric (seconds):
  data$timestamp_duration <- as.numeric(data$timestamp_duration)

  # Filter the GSR data:
  sample_rate <- 10 # Example sample rate (adjust to your data)
  cutoff_freq <- 2 # Example cutoff frequency (1 Hz)

  filtered_gsr_lowpass <- lowpass_filter(data, cutoff_freq, sample_rate)
  data$gsr_filtered_lowpass <- filtered_gsr_lowpass

  # Filter the GSR data (moving average):
  window_size <- 5 # Example window size (adjust as needed; must be odd)

  filtered_gsr_ma <- moving_average_filter(data, window_size)
  data$gsr_filtered_ma <- filtered_gsr_ma

  # Create the plotly plot with all three lines:
  p <- plot_ly(data, x = ~timestamp_duration) %>%
    add_trace(y = ~gsr, type = "scatter", mode = "lines", name = "Original GSR", color = I("#F8766D")) %>%
    add_trace(y = ~gsr_filtered_lowpass, type = "scatter", mode = "lines", name = "Lowpass Filtered GSR", color = I("#619CFF")) %>%
    add_trace(y = ~gsr_filtered_ma, type = "scatter", mode = "lines", name = "Moving Average Filtered GSR", color = I("#00BA38")) %>%
    layout(title = df$files[i],
           xaxis = list(title = "Time (s)"),
           yaxis = list(title = "GSR (uS)"))

  p # Return the plotly plot
})

# Create the scrolling container (same as before):
scrolling_container <- htmltools::tags$div(
  style = "overflow-x: scroll; white-space: nowrap; width: 100%;"
)

# Add each plot to the scrolling container:
for (i in 1:length(plot_list_filtered)) {
  scrolling_container$children[[i]] <- htmltools::div(
    style = "display: inline-block; margin-right: 10px;",
    plot_list_filtered[[i]]
  )
}

# Display the scrolling container:
scrolling_container

Results

Discussion


Report by Vivian Eng

vivian@eadietech.com